#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdlib>
#include <string>
#include <sstream>
#include <fstream>
#include <vector>
#include <set>
#include "pugixml-1.9/src/pugixml.hpp"
#include "pugixml-1.9/src/pugixml.cpp"
#include "riichi.hpp"
using namespace std;
using namespace pugi;

const int player_num = 4; // 玩家数
const int hai_len = 136; // 手牌数组长度

class confrontation // 对局信息
{
private: // 私有成员变量
    unsigned char hai[player_num][hai_len]; // 手牌
    unsigned char discard[player_num][hai_len]; // 弃牌
    unsigned char show[player_num][hai_len]; // 鸣牌
    unsigned char dora[hai_len];
    unsigned char red[player_num][3]; // 红5
    unsigned char reach[player_num]; // 立直情况。0为未立直，1为立直。
    unsigned char show_num[player_num]; // 各家副露数

    vector<string> vec_hai[player_num]; // 记录本局中手牌。每次可以操作时（在update_ostringstream函数中）更新。
    vector<string> vec_discard[player_num][player_num]; // 记录本局中丢牌顺序
    vector<string> vec_dora[player_num]; // 记录本局中dora指示牌
    vector<string> vec_show[player_num][player_num]; // 记录本局中副露情况
    vector<int> vec_reach[player_num]; // 记录本局中reach情况

    set<unsigned char> recently_discard[player_num][player_num]; // 最近一巡四家分别记录的四家舍牌。如recently_discard[a][b]表示以a玩家为一巡的标准，其他四位玩家在该巡内的舍牌。
    set<unsigned char> recently_dora[player_num]; // 最近一巡四家分别记录的dora指示牌。游戏开始时和翻出新dora时更新。
    set<unsigned char> recently_show[player_num][player_num]; // 最近一巡四家分别记录的四家副露。副露时更新。
    unsigned char recently_reach[player_num]; // 最近一巡且没有鸣牌时四家的立直情况。（用于判断一发）

    int field; // 场次
    int point[4]; // 打点
    int my_rank; // 自己的排名。0~3
    int perspective; // 视角，用0~3表示

    struct _state // 可以操作的类型
    {
        unsigned char can_discard; // 弃牌
        unsigned char can_chi;  // 吃
        unsigned char can_pon; // 碰
        unsigned char can_riichi; // 立直
        unsigned char can_kan; // 杠
        unsigned char pad1, pad2, pad3; // 填充位
    } __attribute__ ((aligned (8)));
    _state state[4];
    unsigned long long (&state_ptr)[4] = reinterpret_cast<unsigned long long (&)[4]>(state);

    enum next_op_type {next_op_discard, next_op_chi, next_op_pon, next_op_reach, next_op_else};
    struct _next_op
    {
        next_op_type op; // 下个操作的类型
        int who; // 下个操作的人
        int hai; // 下个操作的牌
    } next_op;

    ostringstream out_ss[4]; // out[0]是弃牌，out[1]是吃，out[2]是碰，out[3]是立直
    
    const char int_to_char[10] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};

private: // 私有成员函数
    void update_point(const char_t *ten);
    inline void clear(); // 清零各个成员变量
    void init(xml_node_iterator &it); // 初始化
    inline void update_my_rank();

    // 麻将术语中英对照：http://www.xqbase.com/other/mahjongg_english.htm
    void num_add(int player, int num); // 摸牌
    void num_remove(int player, int num); // 弃牌
    void show_add_from_other(int my, int other, int num); // 拿了别人一张牌副露
    inline void show_add_from_myself(int player, int num); // 自己副露。（暗杠，加杠）

    inline unsigned char sum_4_char(unsigned char *chr); // 把连续的4个char相加。一次加载，二分相加

    void can_chi(int player, int num);
    void can_pon_and_ming_kan(int player, int num);
    void can_an_kan_jia_kan(int player, int num);
    void can_riichi(int player);

    string num_analyse(int hai_num);
    void discard_analyse(const string &op);
    void hai_analyse(int player, const char *hai); // 解析手牌
    void reach_analyse(xml_node_iterator &it); // 解析立直
    void score_change_analyse(xml_node_iterator &it); // 解析点数变化。用于流局+和牌
    void ba_analyse(xml_node_iterator &it); // 解析供托情况
    void N_analyse(xml_node_iterator &it); // 解析鸣牌情况
    void dora_analyse(xml_node_iterator &it); // 解析新翻出的dora;
    void RYUUKYOKU_analyse(xml_node_iterator &it); // 解析流局
    void AGARI_analyse(xml_node_iterator &it); // 解析和牌

    void analyse_next_op(xml_node_iterator &next_op_it);
    void next_op_discard_analyse(const string &op); // 解析下一个弃牌操作
    void next_op_N_analyse(xml_node_iterator &it); // 解析下一个鸣牌操作
    void next_op_reach_analyse(xml_node_iterator &it); // 解析下一个立直操作

    void update_ostringstream(); // 更新out流，加入当前局面信息

    void hai_to_34(unsigned char short_hai[34]);
    string hai_to_str(unsigned char *hai);
    string set_to_str(set<unsigned char> &hai);

    unsigned int BKDRhash(const char *str);

public:
    confrontation(); // 构造函数。初始化四个csv的表头标题
    void input(const char *path); // 含欲读入xml的路径的构造函数
    void output_csv(string path); // 输出当前out流内容到文件
};

void confrontation::update_point(const char_t *ten)
{
    char buf[32];
    strcpy(buf, ten);
    point[0] = atoi(strtok(buf, ","));
    for(int i = 1; i < 4; i++)
        point[i] = atoi(strtok(NULL, ","));
    
    update_my_rank();
}

void confrontation::clear()
{
    for(int i = 0; i < player_num; i++)
    {
        memset(this->hai[i], 0, hai_len);
        memset(discard[i], 0, hai_len);
        memset(show[i], 0, hai_len);
        memset(red[i], 0, 3);
        vec_hai[i].clear();
        vec_dora[i].clear();
        vec_reach[i].clear();
        recently_dora[i].clear();
        for(int j = 0; j < player_num; j++)
        {
            vec_discard[i][j].clear();
            vec_show[i][j].clear();
            recently_discard[i][j].clear();
            recently_show[i][j].clear();
        }
    }
    memset(dora, 0, hai_len);
    memset(reach, 0, player_num);
    memset(show_num, 0, player_num);
    memset(recently_reach, 0, player_num);
    memset(state, 0, sizeof(state));
}

void confrontation::init(xml_node_iterator &it)
{
    clear();

    auto ait = it->attributes_begin();
    const char_t *seed = (ait++)->value();
    const char_t *ten = (ait++)->value(); // 四家分数

    char buf[32];
    strcpy(buf, seed);
    field            = atoi(strtok(buf, ","));
    int banker_times = atoi(strtok(NULL, ","));
    int reach        = atoi(strtok(NULL, ","));
    int dice1        = atoi(strtok(NULL, ","));
    int dice2        = atoi(strtok(NULL, ","));
    int dora_num     = atoi(strtok(NULL, ","));
    
    dora[dora_num] = 1;
    for(int i = 0; i < player_num; i++)
        recently_dora[i].insert(dora_num); // 更新最近一轮的dora向量

    update_point(ten);

    auto attr = it->attribute("hai0");
    for(int i = 0; i < player_num; i++)
    {
        hai_analyse(i, attr.value());
        attr = attr.next_attribute();
    }
}

void confrontation::update_my_rank()
{
    int rank = 0;
    for(int i = (perspective + 1) % player_num, temp = point[perspective]; i != perspective; i = (i + 1) % player_num)
        if(temp < point[i] || temp == point[i] && i < perspective)
            rank++;
    my_rank = rank;
}

void confrontation::reach_analyse(xml_node_iterator &it) // 解析立直
{
    int my = it->attribute("who").as_int();
    if(*it->attribute("step").value() == '1') // 宣告立直
        reach[my] = 1;
    else // step == 2。放下立直棒
    {
        update_point(it->attribute("ten").value());
        recently_reach[my] = 1;
    }
}

// 麻将术语中英对照：http://www.xqbase.com/other/mahjongg_english.htm
void confrontation::num_add(int player, int num) // 摸牌
{
    int suit = num / (4 * 9); // 花色
    int rem = num % (4 * 9);
    if(suit < 3 && rem == 16)
        red[player][suit] = 1;
    hai[player][num] = 1;
    
    state_ptr[player] = 0; // 摸牌了，初始化所有标志位
    state[player].can_discard = 1; // 摸牌了就可以丢牌了

    if(player == perspective) // 如果是自己就要判断能否立直
    {
        can_an_kan_jia_kan(player, num);
        can_riichi(player);
    }
}

void confrontation::num_remove(int player, int num) // 弃牌
{
    recently_reach[player] = 0; // 转了一圈，回到自己如果不是自摸而是丢牌就没有一发了

    int suit = num / (4 * 9); // 花色
    int rem = num % (4 * 9);
    if(suit < 3 && rem == 16)
        red[player][suit] = 0;

    hai[player][num] = 0;
    discard[player][num] = 1;

    state_ptr[player] = 0; // 弃牌了，初始化所有标志位

    if(player != perspective) // 不是自己丢的就要考虑碰、明杠
        can_pon_and_ming_kan(perspective, num);

    if(player == (perspective + player_num - 1) % player_num) // 如果是上家弃牌就要判断自己能不能吃
        can_chi(perspective, num);
    
    // 加入到各家该轮舍牌的临时set中
    for(int i = 0; i < player_num; i++)
        recently_discard[i][player].insert(num);
}

void confrontation::show_add_from_other(int my, int other, int num) // 拿了别人一张牌副露
{
    int suit = num / (4 * 9); // 花色
    int rem = num % (4 * 9);
    if(suit < 3 && rem == 16)
        red[my][suit] = 0;

    discard[other][num]--;
    show[my][num]++;
    show_num[my]++;

    state_ptr[my] = 0; // 拿了别人一张牌，初始化所有标志位
    if(my == perspective) // 但如果是自己还要准备丢牌
        state[my].can_discard = 1;
    
    // 给四家添加最近副露情况
    for(int i = 0; i < player_num; i++)
        recently_show[i][my].insert(num);
}

inline void confrontation::show_add_from_myself(int player, int num) // 自己副露。（暗杠，加杠）
{
    hai[player][num]--;
    show[player][num]++;

    // 给四家添加最近副露情况
    for(int i = 0; i < player_num; i++)
        recently_show[i][player].insert(num);
}

string confrontation::num_analyse(int hai_num)
{
    static const char suit_name[] = {'m', 'p', 's', 'z'};
    static const char *num_to_char = &int_to_char[1]; // 牌的实际点数是num + 1

    unsigned int temp = hai_num >> 2;
    unsigned int suit = temp / 9; // 花色
    unsigned int num = temp % 9; // 数字

    // 解析本张牌
    string str;
    str = num_to_char[num];
    str += suit_name[suit];

    // 解析红5
    unsigned int red_dora = 0;
    for(int i = 4 * 4, j = 1; i <= 4 * 4 + 2 * 4 * 9; i += 4 * 9, j <<= 1)
        if(i == hai_num)
            red_dora |= j;

    str += ",";
    if(red_dora)
        str += int_to_char[red_dora];

    return str;
}

void confrontation::discard_analyse(const string &op)
{
    int pai = stoi(op.substr(1, op.length() - 1)); // 被操作的那张牌
    char temp = op[0];
    if(temp <= 'G') // 丢弃
        num_remove(temp - 'D', pai);
    else // 摸起
        num_add(temp - 'T', pai);
}

void confrontation::hai_analyse(int player, const char *hai) // 解析手牌
{
    char buf[64], *ptr;
    strcpy(buf, hai);
    
    ptr = strtok(buf, ",");
    while(ptr != NULL)
    {
        num_add(player, atoi(ptr));
        ptr = strtok(NULL, ",");
    }
}

void confrontation::score_change_analyse(xml_node_iterator &it) // 解析点数变化。用于流局+和牌
{
/*     char buf[64];
    str += "  点数变化：";
    strcpy(buf, it->attribute("sc").value()); // 点数变化
    for(int i = 0; i < 4; i++)
    {
        str += "  玩家";
        str += '0' + i;
        str += "原点数：";
        if(i == 0)
            str += strtok(buf, ",");
        else
            str += strtok(NULL, ",");
        str += " 变化点数：";
        str += strtok(NULL, ",");
    } */
}

void confrontation::ba_analyse(xml_node_iterator &it) // 解析供托情况
{
/*     const char *ptr = it->attribute("ba").value();
    str += "  连庄次数：";
    str += ptr[0];
    str += "  立直棒：";
    str += ptr[2]; */
}

void confrontation::N_analyse(xml_node_iterator &it) // 解析鸣牌情况
{
    memset(recently_reach, 0, sizeof(recently_reach)); // 鸣牌了就没有一发了

    int val = it->attribute("m").as_int();
    int my = it->attribute("who").as_int();
    int other = (my + val) & 0x3;

    if(val & 0x4) // 吃。bit2 == 1
    {
        int base_and_called = val >> 10;
        int base = base_and_called / 3; // 吃中最小的那张牌
        int called = base_and_called % 3; // 被吃的那张牌是第几张
        base += (base / 7) << 1; // 花色、配牌的两倍，用于计算牌编码时的偏移
        int chi[3]; // 吃中三张牌的0~140编码
        for(int i = 0, j = 3; i < 3; i++, j += 2)
            chi[i] = ((base + i) << 2) + ((val >> j) & 0x3);

        show_add_from_other(my, other, chi[called]);
        for(int i = 0; i < 3; i++)
            if(i != called)
                show_add_from_myself(my, chi[i]);        
    }
    else if(val & 0x18) // 碰，bit3 == 1；加杠，bit4 == 1
    {
        int base_and_called = val >> 9;
        int base = base_and_called / 3; // 碰的牌
        int called = 3 - base_and_called % 3; // 被碰的那张牌是第几张。这里要用3来减，网上的资料全错
        base <<= 2; // 乘以4，算出4张相同牌中的最小编号
        int unused = (val >> 5) & 0x3; // 碰：同种的四张牌中在碰中没出现的那张；加杠：加杠的牌。

        if(val & 0x8) // 碰，bit3 == 1
        {
            show_add_from_other(my, other, base + called);
            for(int i = 0; i < 4; i++)
                if(i != unused && i != called)
                    show_add_from_myself(my, base + i);
        }
        else // 加杠
            show_add_from_myself(my, base + unused);
    }
    else // 杠。bit2~bit4都为0
    {
        int base_and_called = val >> 8; // 杠的牌
        int base = base_and_called & 0xFFFFFFFC; // 杠的牌同种的四张牌中最小的那张

        if(my == other) // 暗杠
        {
            for(int i = base; i < base + 4; i++)
                show_add_from_myself(my, i);
        }
        else // 明杠
        {
            show_add_from_other(my, other, base_and_called);
            for(int i = base; i < base + 4; i++)
                if(i != base_and_called)
                    show_add_from_myself(my, i);
        }
    }
}

void confrontation::dora_analyse(xml_node_iterator &it) // 解析新翻出的dora
{
    int dora_num = it->attribute("hai").as_int();
    dora[dora_num] = 1;
    for(int i = 0; i < player_num; i++)
        recently_dora[i].insert(dora_num); // 更新最近一轮的dora向量
}

void confrontation::RYUUKYOKU_analyse(xml_node_iterator &it) // 解析流局
{
/*     static const char *type[]      = {"",     "yao9",    "reach4",  "ron3",    "kan4",     "kaze4",   "nm"};
    static const char *type_name[] = {"流局", "九种九牌", "四家立直", "三家和了", "四杠散了", "四风连打", "流局满贯"};
    
    const char *ptr = it->attribute("type").value();

    for(unsigned int i = 0; i < sizeof(type) / sizeof(const char *); i++)
        if(strcmp(ptr, type[i]) == 0)
        {
            str += type_name[i];
            break;
        }

    ba_analyse(it, str);

    str += "  展示手牌：";
    static const char *hai[]      = {"hai0",   "hai1",    "hai2",   "hai3"};
    static const char *hai_name[] = {"玩家0：", "玩家1：", "玩家2：", "玩家3："};
    for(unsigned int i = 0; i < sizeof(hai) / sizeof(const char *); i++)
    {
        ptr = it->attribute(hai[i]).value();
        if(*ptr)
        {
            str += hai_name[i];
            hai_analyse(ptr, str); // 需要展示的手牌
        }
    }

    score_change_analyse(it, str); */
}

void confrontation::AGARI_analyse(xml_node_iterator &it) // 解析和牌
{
/*     str = "玩家";
    str += it->attribute("who").value();
    str += "和牌  ";
    
    char buf[64];
    strcpy(buf, it->attribute("ten").value());
    str += strtok(buf, ",");
    str += "符 ";
    str += strtok(NULL, ",");
    str += "点 ";
    if(*strtok(NULL, ",") == '0')
    {
        str += "自摸  里宝牌：";
        str += num_analyse(atoi(it->attribute("doraHaiUra").value()));
    }
    else
    {
        str += "荣和  放铳者：玩家";
        str += it->attribute("fromWho").value();
    }
    
    ba_analyse(it, str);
    str += "  和牌形态：";
    hai_analyse(it->attribute("hai").value(), str); // 和牌形态
    str += " 和到的牌：";
    str += num_analyse(atoi(it->attribute("machi").value())); // 和到的牌

    score_change_analyse(it, str); */
}

void confrontation::analyse_next_op(xml_node_iterator &next_op_it)
{
    const char *name = next_op_it->name();
    if(name[1] >= '0' && name[1] <= '9') // 如果第二位是数字，说明是摸切，则不带参数，直接保存标题
        next_op_discard_analyse(string(name));
    else
    {
        static const char *op_name[] = {"N", "REACH"};
        void (confrontation::*op_fun[])(xml_node_iterator &it) = {&confrontation::next_op_N_analyse, &confrontation::next_op_reach_analyse};
        
        for(unsigned int i = 0; i < sizeof(op_name) / sizeof(char *); i++)
            if(strcmp(name, op_name[i]) == 0)
            {
                (this->*op_fun[i])(next_op_it);
                break;
            }
    }
}

void confrontation::next_op_discard_analyse(const string &op) // 解析下一个弃牌操作
{
    int hai = stoi(op.substr(1, op.length() - 1)); // 被操作的那张牌
    next_op.hai = hai;

    char temp = op[0];
    if(temp <= 'G') // 丢弃
    {
        next_op.op = next_op_discard;
        next_op.who = temp - 'D';
    }
    else // 摸起
    {
        next_op.op = next_op_else;
        next_op.who = temp - 'T';
    }
}

void confrontation::next_op_N_analyse(xml_node_iterator &it) // 解析下一个鸣牌操作
{
    int val = it->attribute("m").as_int();
    int my = it->attribute("who").as_int();

    next_op.who = my;

    if(val & 0x4) // 吃。bit2 == 1
        next_op.op = next_op_chi;  // next_op.hai。吃不需要写出吃的牌     
    else if(val & 0x8) // 碰，bit3 == 1
        next_op.op = next_op_pon;  // next_op.hai。吃不需要写出吃的牌  
    else // 其他操作。如杠、加杠
        next_op.op = next_op_else;
}

void confrontation::next_op_reach_analyse(xml_node_iterator &it) // 解析下一个立直操作
{
    int my = it->attribute("who").as_int();
    next_op.who = my;
    if(*it->attribute("step").value() == '1') // 宣告立直
        next_op.op = next_op_reach;
    else // step == 2。放下立直棒
        next_op.op = next_op_else;
}

string confrontation::hai_to_str(unsigned char *hai)
{
    static const char suit_name[] = {'m', 'p', 's'};
    
    string str;

    int num = 0;
    bool suit_exist; // 标记该花色是否有牌
    bool hai_exist = false; // 标记这个数组是否有牌

    // 解析非字牌
    for(int suit = 0; suit < 3; suit++)
    {
        suit_exist = false;
        for(unsigned char i = '1'; i <= '9'; i++)
            for(int j = 0; j < 4; j++, num++)
                if(hai[num])
                {
                    str += i;
                    suit_exist = true;
                    hai_exist = true;
                }
        
        if(suit_exist)
            str += suit_name[suit];
    }

    // 解析字牌
    suit_exist = false;
    for(unsigned char i = '1'; i <= '7'; i++)
        for(int j = 0; j < 4; j++, num++)
            if(hai[num])
            {
                str += i;
                suit_exist = true;
                hai_exist = true;
            }
    
    if(suit_exist)
        str += 'z';
    
    // 解析红5
    unsigned int red_dora = 0;
    if(hai_exist)
        for(int i = 4 * 4, j = 1; i <= 4 * 4 + 2 * 4 * 9; i += 4 * 9, j <<= 1)
            if(hai[i])
                red_dora |= j;

    str += ',';
    if(red_dora)
        str += int_to_char[red_dora];

    return str;
}

string confrontation::set_to_str(set<unsigned char> &hai)
{
    if(hai.empty())
        return string(",");

    static const char suit_name[] = {'m', 'p', 's', 'z'};
    static const char *num_to_char = &int_to_char[1]; // 牌的实际点数是num + 1

    // 解析牌
    string temp_str[4]; // 四种不同花色的牌
    for(const auto &i : hai)
    {
        unsigned char temp = i >> 2;
        unsigned char suit = temp / 9; // 花色
        unsigned char num = temp % 9; // 数字
        
        temp_str[suit] += num_to_char[num];
    }

    string str;
    for(int i = 0; i < 4; i++) // 将四种不同花色的牌组合到一起，并加上花色标记字符
        if(!temp_str[i].empty())
            str += temp_str[i] + suit_name[i];

    // 解析红5
    unsigned int red_dora = 0;
    for(int i = 4 * 4, j = 1; i <= 4 * 4 + 2 * 4 * 9; i += 4 * 9, j <<= 1)
        if(hai.find(i) != hai.end())
            red_dora |= j;

    str += ",";
    if(red_dora)
        str += int_to_char[red_dora];

    // hai.clear();

    return str;
}

void confrontation::update_ostringstream()
{
    if(static_cast<unsigned int>(state_ptr[perspective]) == 0) // 只有 弃牌、吃、碰和立直操作时需要输出。所以只用前4个元素（int）
        return;
    
    int i;

    // 更新自家手牌vector
    string str = hai_to_str(hai[perspective]);
    vec_hai[perspective].push_back(str);

    // 更新四家舍牌vector
    for(i = 0; i < player_num; i++)
    {
        str = set_to_str(recently_discard[perspective][i]);
        vec_discard[perspective][i].push_back(str);
    }

    // 更新宝牌指示牌vector
    str = set_to_str(recently_dora[perspective]);
    vec_dora[perspective].push_back(str);

    // 更新四家副露vector
    for(i = 0; i < player_num; i++)
    {
        str = set_to_str(recently_show[perspective][i]);
        vec_show[perspective][i].push_back(str);
    }

    // 输出玩家编号
    str = int_to_char[perspective];

    // 输出自家手牌
    str += ',' + *vec_hai[perspective].rbegin();

    // 输出四家舍牌
    i = perspective;
    do
    {
        str += ',' + hai_to_str(discard[i]);
        i = (i + 1) % player_num;
    }
    while (i != perspective);
        

    // 输出四家副露
    i = perspective;
    do
    {
        str += ',' + hai_to_str(show[i]);
        i = (i + 1) % player_num;
    }
    while (i != perspective);
    

    // 输出宝牌指示牌
    str += ',' + hai_to_str(dora);

    // 输出下家立直，对家立直，上家立直
    for(i = (perspective + 1) % player_num; i != perspective; i = (i + 1) % player_num)
    {
        str += ',';
        if(reach[i])
            str += int_to_char[reach[i]];
    }

    // 输出顺位
    str += ',';
    str += int_to_char[my_rank];
    
    // 输出局数（0是东1局）
    str += ',';
    str += int_to_char[field];

    // 输出最近一巡的宝牌指示牌,最近一巡的宝牌指示牌红5
    str += ',';
    auto it_dora = vec_dora[perspective].rbegin();
    if(it_dora != vec_dora[perspective].rend())
        str += *it_dora++;
    else
        str += ','; 

/*     // 输出自己手牌是否有场风牌，自己手牌是否有自风牌
    int feng[2] = {field >> 2, field & 0x3};// feng[0]场风，feng[1]自风
    const int feng_base = 3 * 4 * 9;
    unsigned char feng_num[] = {feng_base, feng_base + 4, feng_base + 8, feng_base + 12};

    for(i = 0; i < 2; i++)
    {
        unsigned int &exist_feng = *reinterpret_cast<unsigned int *>(&hai[perspective][feng_num[feng[i]]]); // 用于判断是否有对应的风牌
        out << ',';
        if(exist_feng)
            out << '1';
    } */

    // 输出最近一巡下家立直,最近一巡对家立直,最近一巡上家立直
    for(i = (perspective + 1) % player_num; i != perspective; i = (i + 1) % player_num)
    {
        str += ',';
        if(recently_reach[i])
            str += int_to_char[recently_reach[i]];
    }


    // 输出最近六巡的信息
    // 手牌反向迭代器
    auto it_hai = vec_hai[perspective].rbegin();
    it_hai++;

    // 弃牌反向迭代器
    vector<string>::reverse_iterator it_discard[player_num];
    for(i = 0; i < player_num; i++)
        it_discard[i] = vec_discard[perspective][i].rbegin();

    // 副露反向迭代器
    vector<string>::reverse_iterator it_show[player_num];
    for(i = 0; i < player_num; i++)
        it_show[i] = vec_show[perspective][i].rbegin();

    for(int j = 0; j < 6; j++)
    {
        // 输出最近一巡自家手牌,最近一巡自家手牌红5
        str += ',';
        if(it_hai != vec_hai[perspective].rend())
            str += *it_hai++;
        else
            str += ','; 
        // 输出最近一巡自家舍牌,最近一巡自家舍牌红5,最近一巡下家舍牌,最近一巡下家舍牌红5,最近一巡对家舍牌,最近一巡对家舍牌红5,最近一巡上家舍牌,最近一巡上家舍牌红5
        i = perspective;
        do
        {
            str += ',';
            if(it_discard[i] != vec_discard[perspective][i].rend())
                str += *it_discard[i]++;
            else
                str += ','; 
            
            i = (i + 1) % player_num;
        }
        while (i != perspective);

        // 输出最近一巡的下家副露红5,最近一巡的对家副露,最近一巡的对家副露红5,最近一巡的上家副露,最近一巡的上家副露红5
        i = perspective;
        do
        {
            str += ',';
            if(it_show[i] != vec_show[perspective][i].rend())
                str += *it_show[i]++;
            else
                str += ','; 
            
            i = (i + 1) % player_num;
        }
        while (i != perspective);  
    }

    // 输出是否操作
    
    // 输出弃牌操作。是否操作,丢弃的牌,丢弃的牌红5
    if(state[perspective].can_discard)
    {
        out_ss[0] << str << ',';
        if(next_op.op == next_op_discard && next_op.who == perspective) // 如果下一个操作是自己弃牌
            out_ss[0] << "1," << num_analyse(next_op.hai);
        else
            out_ss[0] << ",,";
        out_ss[0] << endl;
    }

    // temp_state[0]为discard，[1]为chi，[2]为pon，[3]为riichi
    unsigned char *temp_state = reinterpret_cast<unsigned char *>(&state[perspective]);

    for(i = 1; i < 4; i++) // 依次输出 吃、碰和立直操作
    {
        if(temp_state[i])
        {
            ostringstream &out = out_ss[i];
            out << str << ',';
            if(next_op.op == i && next_op.who == perspective) // 如果下一个操作是当前要输出的对应操作（循环变量i正好对应枚举变量的值）
                out << '1';
            out << endl;
        }
    }
}

confrontation::confrontation()
{
    string col_name("玩家编号,自家手牌,自家手牌红5,自家舍牌,自家舍牌红5,下家舍牌,下家舍牌红5,对家舍牌,对家舍牌红5,上家舍牌,上家舍牌红5,自家副露,自家副露红5,下家副露,下家副露红5,对家副露,对家副露红5,上家副露,上家副露红5,宝牌指示牌,宝牌指示牌红5,下家立直,对家立直,上家立直,顺位,局数,最近一巡的宝牌指示牌,最近一巡的宝牌指示牌红5,最近一巡下家立直,最近一巡对家立直,最近一巡上家立直,最近一巡自家手牌,最近一巡自家手牌红5,最近一巡自家舍牌,最近一巡自家舍牌红5,最近一巡下家舍牌,最近一巡下家舍牌红5,最近一巡对家舍牌,最近一巡对家舍牌红5,最近一巡上家舍牌,最近一巡上家舍牌红5,最近一巡的自家副露,最近一巡的自家副露红5,最近一巡的下家副露,最近一巡的下家副露红5,最近一巡的对家副露,最近一巡的对家副露红5,最近一巡的上家副露,最近一巡的上家副露红5,最近二巡自家手牌,最近二巡自家手牌红5,最近二巡自家舍牌,最近二巡自家舍牌红5,最近二巡下家舍牌,最近二巡下家舍牌红5,最近二巡对家舍牌,最近二巡对家舍牌红5,最近二巡上家舍牌,最近二巡上家舍牌红5,最近二巡的自家副露,最近二巡的自家副露红5,最近二巡的下家副露,最近二巡的下家副露红5,最近二巡的对家副露,最近二巡的对家副露红5,最近二巡的上家副露,最近二巡的上家副露红5,最近三巡自家手牌,最近三巡自家手牌红5,最近三巡自家舍牌,最近三巡自家舍牌红5,最近三巡下家舍牌,最近三巡下家舍牌红5,最近三巡对家舍牌,最近三巡对家舍牌红5,最近三巡上家舍牌,最近三巡上家舍牌红5,最近三巡的自家副露,最近三巡的自家副露红5,最近三巡的下家副露,最近三巡的下家副露红5,最近三巡的对家副露,最近三巡的对家副露红5,最近三巡的上家副露,最近三巡的上家副露红5,最近四巡自家手牌,最近四巡自家手牌红5,最近四巡自家舍牌,最近四巡自家舍牌红5,最近四巡下家舍牌,最近四巡下家舍牌红5,最近四巡对家舍牌,最近四巡对家舍牌红5,最近四巡上家舍牌,最近四巡上家舍牌红5,最近四巡的自家副露,最近四巡的自家副露红5,最近四巡的下家副露,最近四巡的下家副露红5,最近四巡的对家副露,最近四巡的对家副露红5,最近四巡的上家副露,最近四巡的上家副露红5,最近五巡自家手牌,最近五巡自家手牌红5,最近五巡自家舍牌,最近五巡自家舍牌红5,最近五巡下家舍牌,最近五巡下家舍牌红5,最近五巡对家舍牌,最近五巡对家舍牌红5,最近五巡上家舍牌,最近五巡上家舍牌红5,最近五巡的自家副露,最近五巡的自家副露红5,最近五巡的下家副露,最近五巡的下家副露红5,最近五巡的对家副露,最近五巡的对家副露红5,最近五巡的上家副露,最近五巡的上家副露红5,最近六巡自家手牌,最近六巡自家手牌红5,最近六巡自家舍牌,最近六巡自家舍牌红5,最近六巡下家舍牌,最近六巡下家舍牌红5,最近六巡对家舍牌,最近六巡对家舍牌红5,最近六巡上家舍牌,最近六巡上家舍牌红5,最近六巡的自家副露,最近六巡的自家副露红5,最近六巡的下家副露,最近六巡的下家副露红5,最近六巡的对家副露,最近六巡的对家副露红5,最近六巡的上家副露,最近六巡的上家副露红5,是否操作");
    out_ss[0] << col_name << ",丢弃的牌,丢弃的牌红5" << endl;
    for(int i = 1; i < 4; i++)
        out_ss[i] << col_name << endl;
}

void confrontation::input(const char *path)
{
    xml_document xml;

    if(!xml.load_file(path))
    {
        cout << "读入xml文件失败" << endl;
        return;
    }

    perspective = BKDRhash(path) % player_num; // 通过每一局的唯一标识符的hash值确定当前局随机选择的视角

    xml_node mjloggm = xml.child("mjloggm");

    // tag::code[]
    bool is_init = false;

    int i = 0;
    for (xml_node_iterator it = mjloggm.begin(); it != mjloggm.end(); ++it, i++)
    {
        if(reach[perspective]) // 去掉立直后的数据
            return;
        //cout << i << ' ';

        auto next_it = it;
        if(++next_it != mjloggm.end())
            analyse_next_op(next_it);

        const char *name = it->name();
        //cout << name << endl;
        if(strcmp(name, "INIT") == 0)
        {
            is_init = true;
            init(it);
        }
        else if(is_init)
        {
            if(name[1] >= '0' && name[1] <= '9') // 如果第二位是数字，说明是摸切，则不带参数，直接保存标题
                discard_analyse(string(name));
            else
            {
                static const char *op_name[] = {"N", "REACH", "AGARI", "RYUUKYOKU", "DORA"};
                void (confrontation::*op_fun[])(xml_node_iterator &it) = {&confrontation::N_analyse, &confrontation::reach_analyse, &confrontation::AGARI_analyse, &confrontation::RYUUKYOKU_analyse, &confrontation::dora_analyse};
                
                for(unsigned int i = 0; i < sizeof(op_name) / sizeof(char *); i++)
                    if(strcmp(name, op_name[i]) == 0)
                    {
                        (this->*op_fun[i])(it);
                        break;
                    }
            }
            update_ostringstream();
        }
    }
}

void confrontation::can_chi(int player, int num)
{
    num >>= 2;
    int suit = num / 9; // 花色
    num = num % 9; // 数值 - 1

    int start = max(0, num - 2); // 起始数值 - 1
    int end = min(num + 2, 8) - 2; // 终止数值 - 1
    unsigned int *temp_hai = reinterpret_cast<unsigned int *>(&hai[player][suit * 9 * 4]); // 该花色的起始偏移
    if(suit < 3) // 不是字牌
    {
        for(int i = start; i <= end; i++)
        {
            bool if_can_chi = true;
            for(int j = i; j < i + 3; j++)
                if(j != num && temp_hai[j] == 0) // 不是缺的那一张牌，且该张牌不存在，那么就不能吃
                {
                    if_can_chi = false;
                    break;
                }
            
            if(if_can_chi)
            {
                state[player].can_chi = 1;
                break;
            }
        }
    }
}

unsigned char confrontation::sum_4_char(unsigned char *chr) // 一次加载，二分相加
{
    // 将以下循环展开。否则GCC会读分四次读内存然后加到al
    // for(int i = num; i < num + 4; i++)
    //     count += chr[0][i];
    unsigned int &temp1 = *reinterpret_cast<unsigned int *>(chr);
    unsigned short temp2 = static_cast<unsigned short>(temp1) + static_cast<unsigned short>((temp1 >> 16));
    unsigned char count = static_cast<unsigned char>(temp2) + static_cast<unsigned char>((temp2 >> 8));
    return count;
}

void confrontation::can_pon_and_ming_kan(int player, int num)
{
    num &= 0xFFFFFFFC;

    switch(sum_4_char(&hai[player][num]))
    {
        case 2:
            state[player].can_pon = 1;
            break;
        case 3:
            state[player].can_pon = 1;
            state[player].can_kan = 1;
            break;
    } 
}

void confrontation::can_an_kan_jia_kan(int player, int num)
{
    num &= 0xFFFFFFFC;

    if(sum_4_char(&hai[player][num]) == 4 || sum_4_char(&discard[player][num]) == 3) // 能暗杠就不可能加杠了，因而前半部分判断暗杠，后半部分判断加杠
        state[player].can_kan = 1;
}

void confrontation::hai_to_34(unsigned char short_hai[34])
{
    for(int i = 0, j = 0; i < hai_len; i += 4, j++) // 4路循环展开，SSE指令集
    {
        short_hai[j] += hai[0][i];
        short_hai[j] += hai[0][i + 1];
        short_hai[j] += hai[0][i + 2];
        short_hai[j] += hai[0][i + 3];
    }
}

void confrontation::output_csv(string path) // 输出当前out流内容到文件
{
    string postfix[4] = {"_discard.csv", "_chi.csv", "_pon.csv", "_riichi.csv"};

    ofstream fout;
    for(int i = 0; i < 4; i++)
    {
        fout.open(path + postfix[i]);
        fout << out_ss[i].str();
        fout.close();
    }
}

void confrontation::can_riichi(int player)
{
    unsigned char temp_hai[34] = {0};
    hai_to_34(temp_hai);
    if(show_num[player] == 0 && riichi::calcshanten(temp_hai, 0) == 0)
        state[player].can_riichi = 1;
}

unsigned int confrontation::BKDRhash(const char *str)
{
    unsigned int seed = 131;
    unsigned int hash = 0;
    while(*str != '.') // 解析到文件名的'.'为止
        hash = hash * seed + (*str++);
    return hash;
}